<?php
namespace PhalApi;

/**
 * Response 响应类
 *
 * - 拥有各种结果返回状态 ，以及对返回结果 的格式化
 * - 其中：200成功，400非法请求，500服务器错误
 *
 * @package     PhalApi\Response
 * @license     http://www.phalapi.net/license GPL 协议
 * @link        http://www.phalapi.net/
 * @author      dogstar <chanzonghuang@gmail.com> 2014-10-02
 */

abstract class Response {

	/**
	 * @var int $ret 返回状态码，其中：200成功，400非法请求，500服务器错误
	 */
    protected $ret = 200;
    
    /**
     * @var array 待返回给客户端的数据
     */
    protected $data = array();
    
    /**
     * @var string $msg 错误返回信息
     */
    protected $msg = '';
    
    /**
     * @var array $headers 响应报文头部
     */
    protected $headers = array();

    /**
     * @var array $debug 调试信息
     */
    protected $debug = array();

    /**
     * @var array 自定义额外的结果数据
     */
    protected $extraResult = array();

    /** ------------------ setter ------------------ **/

    /**
     * 设置返回状态码
     * @param int $ret 返回状态码，其中：200成功，400非法请求，500服务器错误
     * @return Response
     */
    public function setRet($ret) {
    	$this->ret = $ret;
    	return $this;
    }
    
    /**
     * 设置返回数据
     * @param array/string $data 待返回给客户端的数据，建议使用数组，方便扩展升级
     * @return Response
     */
    public function setData($data) {
    	$this->data = $data;
    	return $this;
    }
    
    /**
     * 设置错误信息
     * @param string $msg 错误信息
     * @return Response
     */
    public function setMsg($msg) {
    	$this->msg = $msg;
    	return $this;
    }

    /**
     * 设置调试信息
     * @param   string  $key        键值标识
     * @param   mixed   $value      调试数据
     * @return  Response
     */
    public function setDebug($key, $value) {
        if (DI()->debug) {
            $this->debug[$key] = $value;
        }
        return $this;
    }

    /**
     * 设置额外的根节点返回结果
     * @param   string  $key        根节点键值，应避免和系统的 ret/msg/data/debug 重复
     * @param   mixed   $value      自定义数据
     * @return  Response
     */
    public function addResult($key, $value) {
        $this->extraResult[$key] = $value;
        return $this;
    }

    /**
     * 添加报文头部
     * @param string $key 名称
     * @param string $content 内容
     */
    public function addHeaders($key, $content) {
    	$this->headers[$key] = $content;
    }

    /** ------------------ 结果输出 ------------------ **/

    /**
     * 结果输出
     */
    public function output() {
        // Step 1. 头部header处理
    	$this->handleHeaders($this->headers);

        // Step 2. 获取数据结果
        $rs = $this->getResult();

        // Step 3. 结构体字段映射
        $rs = $this->mapStructure($rs);

        // Step 4. 序列化结果
        $rs = $this->formatResult($rs);

        // Step 5. 返回输出结果
    	$this->echoResult($rs);
    }

    /**
     * 结构体字段映射
     */
    protected function mapStructure($rs) {
        $map = $this->getStructureMapConfig();
        foreach ($rs as $key => $val) {
            if (!isset($map[$key]) || $key == $map[$key]) {
                continue;
            }
            $rs[$map[$key]] = $val;
            unset($rs[$key]);
        }
        return $rs;
    }

    /**
     * 获取返回字段映射配置，在线接口文档或外部需要展示的场景也需要使用，故用public
     */
    public function getStructureMapConfig() {
        $structureMap = \PhalApi\DI()->config->get('sys.response.structure_map', array());
        $map = array(
            'ret'   => 'ret',
            'data'  => 'data',
            'msg'   => 'msg',
            'debug' => 'debug',
        );

        foreach ($structureMap as $oldKey => $newKey) {
            if (!isset($map[$oldKey]) || empty($newKey)) {
                continue;
            }

            $map[$oldKey] = $newKey;
        }

        return $map;
    }

    protected function echoResult($rs) {
        echo $rs;
    }
    
    /** ------------------ getter ------------------ **/
    
    /**
     * 根据状态码调整Http响应状态码
     */
    public function adjustHttpStatus() {
        $httpStatus = array(
            100 => 'Continue',
            101 => 'Switching Protocols',
            200 => 'OK',
            201 => 'Created',
            202 => 'Accepted',
            203 => 'Non-Authoritative Information',
            204 => 'No Content',
            205 => 'Reset Content',
            206 => 'Partial Content',
            300 => 'Multiple Choices',
            301 => 'Moved Permanently',
            302 => 'Found',
            303 => 'See Other',
            304 => 'Not Modified',
            305 => 'Use Proxy',
            307 => 'Temporary Redirect',
            400 => 'Bad Request',
            401 => 'Unauthorized',
            402 => 'Payment Required',
            403 => 'Forbidden',
            404 => 'Not Found',
            405 => 'Method Not Allowed',
            406 => 'Not Acceptable',
            407 => 'Proxy Authentication Required',
            408 => 'Request Time-out',
            409 => 'Conflict',
            410 => 'Gone',
            411 => 'Length Required',
            412 => 'Precondition Failed',
            413 => 'Request Entity Too Large',
            414 => 'Request-URI Too Large',
            415 => 'Unsupported Media Type',
            416 => 'Requested range not satisfiable',
            417 => 'Expectation Failed',
            500 => 'Internal Server Error',
            501 => 'Not Implemented',
            502 => 'Bad Gateway',
            503 => 'Service Unavailable',
            504 => 'Gateway Time-out',
            505 => 'HTTP Version not supported',
        );

        $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.1';
        $str = "{$protocol} {$this->ret} " . (isset($httpStatus[$this->ret]) ? $httpStatus[$this->ret] : 'Unknown Http Status Code');
        @header($str);

        return $this;
    }

    /**
     * 获取返回结果
     */
    public function getResult() {
        $rs = array(
            'ret'   => $this->ret,
            'data'  => is_array($this->data) && empty($this->data) ? (object)$this->data : $this->data, // # 67 优化
            'msg'   => $this->msg,
        );

        if (!empty($this->debug)) {
            $rs['debug'] = $this->debug;
        }

        // 合并自定义结果
        $rs = array_merge($rs, $this->extraResult);

        return $rs;
    }

	/**
	 * 获取头部
	 * 
	 * @param string $key 头部的名称
	 * @return string/array 对应的内容，不存在时返回NULL，$key为NULL时返回全部
	 */
    public function getHeaders($key = NULL) {
        if ($key === NULL) {
            return $this->headers;
        }

        return isset($this->headers[$key]) ? $this->headers[$key] : NULL;
    }

    /** ------------------ 内部方法 ------------------ **/

    protected function handleHeaders($headers) {
    	foreach ($headers as $key => $content) {
    		@header($key . ': ' . $content);
    	}
    }

    /**
     * 格式化需要输出返回的结果
     *
     * @param array $result 待返回的结果数据
     *
     * @see Response::getResult()
     */
    abstract protected function formatResult($result);
}
